
#include "nane/ScriptSystem/lua/luascriptmodule.h"
#include "nane/ScriptSystem/lua/LuaScriptObject.h"

#include "nane/core/MemoryStream.h"
#include "nane/core/IO.h"

#include <cstring>
#include <string>

extern "C"
{
    #include "lualib.h"
}

#include "nane/core/assert.h"
#include "nane/core/log.h"
#include "nane/core/IFileSystem.h"

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    const uint32 c_maxStackArgsBufferSize = 20 * 8;
    ////////////////////////////////////////////////////////////////////////////////////////////////
    struct LuaReaderInfo
    {
        IInput* input;
        char* buffer;
        size_t bufferSize;
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const char * s_lua_Reader_func( lua_State* _luaState, void* _data, size_t* _size )
    {
        LuaReaderInfo* info = reinterpret_cast<LuaReaderInfo*>(_data);
        int32 bytesRead = info->input->Read(reinterpret_cast<byte*>(info->buffer), info->bufferSize);
        *_size = static_cast<size_t>(bytesRead);
        return info->buffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static int s_luaCFunctionCallback( lua_State* _state )
    {
        int ret = 0;
        lua_getglobal( _state, "_NANE_MODULE" );
        LuaScriptModule* module = static_cast<LuaScriptModule*>(lua_touserdata(_state, lua_gettop(_state)));
        NANE_ASSERT( module != NULL, "module must be valid" );
        lua_pop( _state, 1 );
        const char* functionName = lua_tostring(_state, lua_upvalueindex(1));
        if( module != NULL )
        {
            ret = module->OnCFunction(_state, functionName);
        }
        return ret;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    LuaScriptModule::LuaScriptModule( const char* _name )
        : m_state( NULL )
        , m_name( _name )
        , m_runRef( 0 )
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    LuaScriptModule::~LuaScriptModule()
    {
        if( m_state != NULL )
        {
            lua_close(m_state);
            m_state = NULL;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool LuaScriptModule::Invoke( IInput* _args, IOutput* _results )
    {
        return false;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    const char* LuaScriptModule::ToString()
    {
        return "LUA Script Module";
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IScriptObject* LuaScriptModule::ImportObject( const char* _objectName )
    {
        NANE_ASSERT( m_state != NULL, "Script Module must be initialized" );
        lua_getglobal(m_state, _objectName);
        LuaScriptObject* luaScriptObject = NULL;
        if( lua_isnil(m_state, -1) == false )
        {
            luaScriptObject = new LuaScriptObject(m_state);
        }
        else
        {
            lua_pop(m_state, 1);
            LOG_ERROR("'%1' object not found. can't import") << _objectName;
        }
        return luaScriptObject;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IScriptObject* LuaScriptModule::ExportFunction( const char* _className, const char* _functionName, IScriptFunction* _cfunction )
    {
        NANE_ASSERT( m_state != NULL, "Script Module must be initialized" );
        LuaScriptObject* luaScriptObject = NULL;
        if( m_state == NULL )
        {
            return NULL;
        }

        std::string fullFunctionName;
        bool classScope = _className != NULL && _className[0] != '\0';
        if( classScope == true )
        {
            fullFunctionName = _className;
            fullFunctionName.push_back('.');
        }
        fullFunctionName += _functionName;

        if( classScope == true )
        {
            lua_getglobal(m_state, _className);
            if( lua_isnil(m_state, -1) == true )
            {
                lua_pop(m_state, 1);
                lua_newtable(m_state);
                lua_pushvalue(m_state, -1);
                lua_setglobal(m_state, _className);
            }
            lua_pushstring(m_state, _functionName);
        }

        lua_pushstring(m_state, fullFunctionName.c_str());
        lua_pushcclosure(m_state, &s_luaCFunctionCallback, 1);
        lua_pushvalue(m_state, -1);

        luaScriptObject = new LuaScriptObject(m_state);

        if( classScope == true )
        {
            lua_settable(m_state, -3);
            lua_pop(m_state, 1);
        }
        else
        {
            lua_setglobal(m_state, _functionName);
        }
        
        TExportFunctionMap::iterator it_find = m_exportFunctionMap.find(fullFunctionName);
        if( it_find != m_exportFunctionMap.end() )
        {
            m_exportFunctionMap.erase(it_find);
        }
        m_exportFunctionMap.insert(std::make_pair(fullFunctionName, _cfunction));

        return luaScriptObject;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void LuaScriptModule::ReleaseObject( IScriptObject* _object )
    {
        LuaScriptObject* luaScriptObject = static_cast<LuaScriptObject*>(_object);
        if( _object != NULL )
        {
            NANE_ASSERT_R( m_state == luaScriptObject->m_state, "IScriptObject must valid LuaScriptObject instance" );
            //if( luaScriptObject->rep != NULL )
            //    delete luaScriptObject->rep;
            delete luaScriptObject;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool LuaScriptModule::Initialize()
    {
        NANE_ASSERT( m_state == NULL, "Script Module must not be initialized" );
        m_state = luaL_newstate();
        if( m_state )
        {
            lua_pushcfunction(m_state, luaopen_base);
            lua_pushstring(m_state, "");
            lua_call(m_state, 1, 0);
            lua_pushcfunction(m_state, luaopen_math);
            lua_pushstring(m_state, LUA_MATHLIBNAME);
            lua_call(m_state, 1, 0);
            lua_pushcfunction(m_state, luaopen_table);
            lua_pushstring(m_state, LUA_TABLIBNAME);
            lua_call(m_state, 1, 0);
        }
        return m_state != NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool LuaScriptModule::Load( IInput* _input )
    {
        NANE_ASSERT( m_state != NULL, "Script Module must be initialized" );
        if( m_state != NULL )
        {
            char buffer[LUAL_BUFFERSIZE];
            LuaReaderInfo info = { _input, buffer, LUAL_BUFFERSIZE };
            if( lua_load(m_state, &s_lua_Reader_func, &info, m_name.c_str()) != 0 )
            {
                LOG_ERROR("%1") << lua_tostring(m_state, -1);
                lua_close(m_state);
                m_state = NULL;
            }
            else
            {
                lua_pushlightuserdata(m_state, this);
                lua_setglobal(m_state, "_NANE_MODULE");
                m_runRef = luaL_ref(m_state, LUA_REGISTRYINDEX);
                Run();
            }
        }
        return m_state != NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    int LuaScriptModule::OnCFunction( lua_State* _state, const char* _functionName )
    {
        NANE_ASSERT( m_state == _state, "Lua state must be same as module" );

        int nargs = lua_gettop(m_state);

        byte argsBuffer[c_maxStackArgsBufferSize];
        MemoryStream argsBufferStream(argsBuffer, c_maxStackArgsBufferSize);

        int poped = LuaScriptObject::s_luaPopResults(_state, &argsBufferStream);
        argsBufferStream.Seek(0);
        TExportFunctionMap::iterator it_find = m_exportFunctionMap.find(_functionName);
        if( it_find == m_exportFunctionMap.end() )
        {
            LOG_ERROR("Extern function '%1' not found") << _functionName;
            lua_pop(_state, poped);
        }
        else
        {
            IScriptFunction*& cfunction = it_find->second;
            byte resulstBuffer[c_maxStackArgsBufferSize];
            MemoryStream resultsBufferStream(resulstBuffer, c_maxStackArgsBufferSize);
            bool invokeSucceeded = cfunction->Invoke(&argsBufferStream, &resultsBufferStream);
            lua_pop(_state, poped);
            if( invokeSucceeded == true )
            {
                resultsBufferStream.Seek(0);
                LuaScriptObject::s_luaPushValues(m_state, &resultsBufferStream);
            }
            else
            {
                // TODO: log backtrace
            }
        }
        return nargs;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool LuaScriptModule::Run()
    {
        lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_runRef);
        lua_pcall(m_state, 0, 0, 0);
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
